home *** CD-ROM | disk | FTP | other *** search
- #
- # The Python Imaging Library.
- # $Id: Image.py,v 1.1.1.2 1999/01/13 09:40:19 sjoerd Exp $
- #
- # the Image class wrapper
- #
- # history:
- # 95-09-09 fl Created
- # 96-03-11 fl PIL release 0.0
- # 96-04-30 fl PIL release 0.1b1
- # 96-05-27 fl PIL release 0.1b2
- # 96-10-04 fl PIL release 0.2a1
- # 96-11-04 fl PIL release 0.2b1
- # 96-12-08 fl PIL release 0.2b2
- # 96-12-16 fl PIL release 0.2b3
- # 97-01-14 fl PIL release 0.2b4
- # 97-06-02 fl PIL release 0.3a1
- # 97-08-27 fl PIL release 0.3a2
- # 98-02-02 fl PIL release 0.3a3
- # 98-03-16 fl PIL release 0.3a4
- # 98-07-02 fl PIL release 0.3b1
- # 98-07-17 fl PIL release 0.3b2
- # 99-01-01 fl PIL release 1.0b1
- #
- # Copyright (c) Secret Labs AB 1997-99.
- # Copyright (c) Fredrik Lundh 1995-97.
- #
- # See the README file for information on usage and redistribution.
- #
-
- VERSION = "1.0b1"
-
- class _imaging_not_installed:
- # module placeholder
- def __getattr__(self, id):
- raise ImportError, "The _imaging C module is not installed"
-
- try:
- # If the _imaging C module is not present, you can only use the
- # "open" function to identify files. Note that other modules
- # should not refer to _imaging directly; import Image and use the
- # Image.core variable instead.
- import _imaging
- core = _imaging
- del _imaging
- except ImportError:
- core = _imaging_not_installed()
-
- import ImagePalette
- import os, string
-
- # type stuff
- from types import IntType, StringType, TupleType
-
- def isStringType(t):
- return type(t) is StringType
-
- def isTupleType(t):
- return type(t) is TupleType
-
- def isImageType(t):
- return hasattr(t, "im")
-
- from operator import isNumberType, isSequenceType
-
- #
- # Debug level
-
- DEBUG = 0
-
- #
- # Constants (also defined in _imagingmodule.c!)
-
- NONE = 0
-
- # transpose
- FLIP_LEFT_RIGHT = 0
- FLIP_TOP_BOTTOM = 1
- ROTATE_90 = 2
- ROTATE_180 = 3
- ROTATE_270 = 4
-
- # transforms
- AFFINE = 0
- EXTENT = 1
- PERSPECTIVE = 2 # Not yet implemented
- QUAD = 3
- MESH = 4
-
- # resampling
- NONE = 0
- NEAREST = 0
- ANTIALIAS = 1 # Not yet implemented
- BILINEAR = 2
- BICUBIC = 3
-
- # dithers
- NONE = 0
- NEAREST = 0
- ORDERED = 1 # Not yet implemented
- RASTERIZE = 2 # Not yet implemented
- FLOYDSTEINBERG = 3 # default
-
- # palettes/quantizers
- WEB = 0
- ADAPTIVE = 1
-
- # categories
- NORMAL = 0
- SEQUENCE = 1
- CONTAINER = 2
-
- # --------------------------------------------------------------------
- # Registries
-
- ID = []
- OPEN = {}
- MIME = {}
- SAVE = {}
- EXTENSION = {}
-
- # --------------------------------------------------------------------
- # Modes supported by this version
-
- _MODEINFO = {
-
- # official modes
- "1": ("L", "L", ("1",)),
- "L": ("L", "L", ("L",)),
- "I": ("L", "I", ("I",)),
- "F": ("L", "F", ("F",)),
- "P": ("RGB", "L", ("P",)),
- "RGB": ("RGB", "L", ("R", "G", "B")),
- "RGBX": ("RGB", "L", ("R", "G", "B", "X")),
- "RGBA": ("RGB", "L", ("R", "G", "B", "A")),
- "CMYK": ("RGB", "L", ("C", "M", "Y", "K")),
- "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")),
-
- # experimental modes
- "I;16": ("L", "I", ("I",)),
- "I;16B": ("L", "I", ("I",)),
-
- }
-
- MODES = _MODEINFO.keys()
- MODES.remove("I;16")
- MODES.remove("I;16B")
- MODES.sort()
-
- def getmodebase(mode):
- return _MODEINFO[mode][0]
-
- def getmodetype(mode):
- return _MODEINFO[mode][1]
-
- def getmodebands(mode):
- return len(_MODEINFO[mode][2])
-
- # --------------------------------------------------------------------
- # Helpers
-
- _initialized = 0
-
- def preinit():
- "Load standard file format drivers."
-
- global _initialized
- if _initialized >= 1:
- return
-
- for m in ("BmpImagePlugin", "GifImagePlugin", "JpegImagePlugin",
- "PpmImagePlugin", "TiffImagePlugin"):
- try:
- __import__(m)
- except ImportError:
- pass # ignore missing driver for now
-
- _initialized = 1
-
- def init():
- "Load all file format drivers."
-
- global _initialized
- if _initialized >= 2:
- return
-
- import os, sys
-
- # only check directories (including current, if present in the path)
- for path in filter(os.path.isdir, sys.path):
- for file in os.listdir(path):
- if file[-14:] == "ImagePlugin.py":
- p, f = os.path.split(file)
- f, e = os.path.splitext(f)
- try:
- sys.path.insert(0, path)
- try:
- __import__(f)
- finally:
- del sys.path[0]
- except ImportError:
- if DEBUG:
- print "Image: failed to import",
- print f, ":", sys.exc_value
-
- if OPEN or SAVE:
- _initialized = 2
-
-
- # --------------------------------------------------------------------
- # Codec factories (used by tostring/fromstring and ImageFile.load)
-
- def _getdecoder(mode, decoder_name, args, extra = ()):
-
- # tweak arguments
- if args is None:
- args = ()
- elif type(args) != TupleType:
- args = (args,)
-
- try:
- # get decoder
- decoder = getattr(core, decoder_name + "_decoder")
- # print decoder, (mode,) + args + extra
- return apply(decoder, (mode,) + args + extra)
- except AttributeError:
- raise IOError, "decoder %s not available" % decoder_name
-
- def _getencoder(mode, encoder_name, args, extra = ()):
-
- # tweak arguments
- if args is None:
- args = ()
- elif type(args) != TupleType:
- args = (args,)
-
- try:
- # get encoder
- encoder = getattr(core, encoder_name + "_encoder")
- # print encoder, (mode,) + args + extra
- return apply(encoder, (mode,) + args + extra)
- except AttributeError:
- raise IOError, "encoder %s not available" % encoder_name
-
-
- # --------------------------------------------------------------------
- # Simple expression analyzer
-
- class _E:
- def __init__(self, data): self.data = data
- def __coerce__(self, other): return self, _E(other)
- def __add__(self, other): return _E((self.data, "__add__", other.data))
- def __mul__(self, other): return _E((self.data, "__mul__", other.data))
-
- def _getscaleoffset(expr):
- stub = ["stub"]
- data = expr(_E(stub)).data
- try:
- (a, b, c) = data # simplified syntax
- if (a is stub and b == "__mul__" and isNumberType(c)):
- return c, 0.0
- if (a is stub and b == "__add__" and isNumberType(c)):
- return 1.0, c
- except TypeError: pass
- try:
- ((a, b, c), d, e) = data # full syntax
- if (a is stub and b == "__mul__" and isNumberType(c) and
- d == "__add__" and isNumberType(e)):
- return c, e
- except TypeError: pass
- raise ValueError, "illegal expression"
-
-
- # --------------------------------------------------------------------
- # Implementation wrapper
-
- import os
-
- class Image:
-
- format = None
- format_description = None
-
- def __init__(self):
- self.im = None
- self.mode = ""
- self.size = (0,0)
- self.palette = None
- self.info = {}
- self.category = NORMAL
-
- def __setattr__(self, id, value):
- if id == "palette":
- pass # print "set", id, value
- self.__dict__[id] = value
-
- def _makeself(self, im):
- new = Image()
- new.im = im
- new.mode = im.mode
- new.size = im.size
- new.palette = self.palette
- new.info = self.info
- return new
-
- def _dump(self, file = None):
- import tempfile
- if not file:
- file = tempfile.mktemp()
- self.load()
- self.im.save_ppm(file)
- return file
-
- def tostring(self, encoder_name = "raw", *args):
- "Return image as a binary string"
-
- # may pass tuple instead of argument list
- if len(args) == 1 and isTupleType(args[0]):
- args = args[0]
-
- if encoder_name == "raw" and args == ():
- args = self.mode
-
- self.load()
-
- # unpack data
- e = _getencoder(self.mode, encoder_name, args)
- e.setimage(self.im)
-
- data = []
- while 1:
- l, s, d = e.encode(65536)
- data.append(d)
- if s:
- break
- if s < 0:
- raise RuntimeError, "encoder error %d in tostring" % s
-
- return string.join(data, "")
-
- def tobitmap(self, name = "image"):
- "Return image as an XBM bitmap"
-
- self.load()
- if self.mode != "1":
- raise ValueError, "not a bitmap"
- data = self.tostring("xbm")
- return string.join(["#define %s_width %d\n" % (name, self.size[0]),
- "#define %s_height %d\n"% (name, self.size[1]),
- "static char %s_bits[] = {\n" % name, data, "};"], "")
-
- def fromstring(self, data, decoder_name = "raw", *args):
- "Load data to image from binary string"
-
- # may pass tuple instead of argument list
- if len(args) == 1 and isTupleType(args[0]):
- args = args[0]
-
- # default format
- if decoder_name == "raw" and args == ():
- args = self.mode
-
- # unpack data
- d = _getdecoder(self.mode, decoder_name, args)
- d.setimage(self.im)
- s = d.decode(data)
-
- if s[0] >= 0:
- raise ValueError, "not enough image data"
- if s[1] != 0:
- raise ValueError, "cannot decode image data"
-
- def load(self, modify=0):
- if self.im and self.palette and self.palette.rawmode:
- self.im.putpalette(self.palette.rawmode, self.palette.data)
- self.palette.mode = "RGB"
- self.palette.rawmode = None
- if self.info.has_key("transparency"):
- self.im.putpalettealpha(self.info["transparency"], 0)
- self.palette.mode = "RGBA"
-
- #
- # function wrappers
-
- def convert(self, mode=None, data=None, dither=None,
- palette=WEB, colors=256):
- "Convert to other pixel format"
-
- if not mode:
- # determine default mode
- if self.mode == "P":
- mode = self.palette.mode
- else:
- return self.copy()
-
- self.load()
-
- if data:
- # matrix conversion
- if mode not in ("L", "RGB"):
- raise ValueError, "illegal conversion"
- im = self.im.convert_matrix(mode, data)
- return self._makeself(im)
-
- if mode == "P" and palette == ADAPTIVE:
- im = self.im.quantize(colors)
- return self._makeself(im)
-
- # colourspace conversion
- if dither is None:
- dither = FLOYDSTEINBERG
-
- try:
- im = self.im.convert(mode, dither)
- except ValueError:
- try:
- # normalize source image and try again
- im = self.im.convert(getmodebase(self.mode))
- im = im.convert(mode, dither)
- except KeyError:
- raise ValueError, "illegal conversion"
-
- return self._makeself(im)
-
- def quantize(self, colours=256, method=0, kmeans=0):
-
- # methods:
- # 0 = median cut
- # 1 = maximum coverage
-
- # NOTE: this functionality will be moved to the extended
- # quantizer interface in a later versions of PIL.
-
- self.load()
- im = self.im.quantize(colours, method, kmeans)
- return self._makeself(im)
-
- def copy(self):
- "Copy raster data"
-
- self.load()
- im = self.im.copy()
- return self._makeself(im)
-
- def crop(self, box = None):
- "Crop region from image"
-
- self.load()
- if box is None:
- return self.copy()
-
- # delayed operation
- return _ImageCrop(self, box)
-
- def draft(self, mode, size):
- "Configure image decoder"
-
- pass
-
- def filter(self, kernel):
- "Apply environment filter to image"
-
- if self.mode == "P":
- raise ValueError, "cannot filter palette images"
- self.load()
- id = kernel.id
- if self.im.bands == 1:
- return self._makeself(self.im.filter(id))
- # fix to handle multiband images since _imaging doesn't
- ims = []
- for c in range(self.im.bands):
- ims.append(self._makeself(self.im.getband(c).filter(id)))
- return merge(self.mode, ims)
-
- def getbands(self):
- "Get band names"
-
- return _MODEINFO[self.mode][2]
-
- def getbbox(self):
- "Get bounding box of actual data (non-zero pixels) in image"
-
- self.load()
- return self.im.getbbox()
-
- def getdata(self, band = None):
- "Get image data as sequence object."
-
- self.load()
- if band is not None:
- return self.im.getband(band)
- return self.im # could be abused
-
- def getextrema(self):
- "Get min/max value"
-
- self.load()
- return self.im.getextrema()
-
- def getpixel(self, xy):
- "Get pixel value"
-
- self.load()
- return self.im.getpixel(xy)
-
- def getprojection(self):
- "Get projection to x and y axes"
-
- self.load()
- x, y = self.im.getprojection()
- return map(ord, x), map(ord, y)
-
- def histogram(self, mask=None, extrema=None):
- "Take histogram of image"
-
- self.load()
- if mask:
- mask.load()
- return self.im.histogram((0, 0), mask.im)
- if self.mode in ("I", "F"):
- if extrema is None:
- extrema = self.getextrema()
- return self.im.histogram(extrema)
- return self.im.histogram()
-
- def offset(self, xoffset, yoffset = None):
- "Offset image in horizontal and/or vertical direction"
-
- if yoffset is None:
- yoffset = xoffset
- self.load()
- return self._makeself(self.im.offset(xoffset, yoffset))
-
- def paste(self, im, box = None, mask = None):
- "Paste other image into region"
-
- if box is None:
- # all of image
- box = (0, 0) + self.size
-
- if not isImageType(im):
- if len(box) == 2:
- box = box + self.size
- im = new(self.mode, (box[2]-box[0], box[3]-box[1]), im)
-
- elif len(box) == 2:
- # lower left corner given
- box = box + (box[0]+im.size[0], box[1]+im.size[1])
-
- im.load()
- self.load(1)
-
- # fix to handle conversion when pasting (should be
- # done via adapters)
- if self.mode != im.mode:
- im = im.convert(self.mode)
-
- if mask:
- mask.load()
- self.im.paste(im.im, box, mask.im)
- else:
- self.im.paste(im.im, box)
-
- def point(self, lut, mode = None):
- "Map image through lookup table"
-
- if self.mode in ("I", "F"):
- # floating point; lut must be a valid expression
- scale, offset = _getscaleoffset(lut)
- self.load()
- im = self.im.point_transform(scale, offset);
- else:
- # integer image; use lut and mode
- self.load()
- if not isSequenceType(lut):
- # if it isn't a list, it should be a function
- lut = map(lut, range(256)) * self.im.bands
- im = self.im.point(lut, mode)
-
- return self._makeself(im)
-
- def putalpha(self, im):
- "Set alpha layer"
-
- if self.mode != "RGBA" or im.mode not in ("1", "L"):
- raise ValueError, "illegal image mode"
-
- im.load()
- self.load(1)
-
- if im.mode == "1":
- im = im.convert("L")
-
- self.im.putband(im.im, 3)
-
- def putdata(self, data, scale = 1.0, offset = 0.0):
- "Put data from a sequence object into an image."
-
- self.load(1) # hmm...
- self.im.putdata(data, scale, offset)
-
- def putpalette(self, data, rawmode = "RGB"):
- "Put palette data into an image."
-
- if self.mode not in ("L", "P"):
- raise ValueError, "illegal image mode"
- if type(data) != StringType:
- data = string.join(map(chr, data), "")
- self.mode = "P"
- self.palette = ImagePalette.raw(rawmode, data)
- self.palette.mode = "RGB"
-
- def putpixel(self, xy, value):
- "Set pixel value"
-
- self.load(1)
- return self.im.putpixel(xy, value)
-
- def resize(self, size, resample = NEAREST):
- "Resize image"
-
- if resample not in (NEAREST, BILINEAR, BICUBIC):
- raise ValueError, "unknown resampling filter"
-
- self.load()
- im = self.im.resize(size, resample)
- return self._makeself(im)
-
- def rotate(self, angle, resample = NEAREST):
- "Rotate image. Angle given as degrees counter-clockwise."
-
- if resample not in (NEAREST, BILINEAR, BICUBIC):
- raise ValueError, "unknown resampling filter"
-
- self.load()
- im = self.im.rotate(angle, resample)
- return self._makeself(im)
-
- def save(self, fp, format = None, **params):
- "Save image to file or stream"
-
- if isStringType(fp):
- import __builtin__
- filename = fp
- fp = __builtin__.open(fp, "wb")
- close = 1
- else:
- filename = ""
- close = 0
-
- self.encoderinfo = params
- self.encoderconfig = ()
-
- self.load()
-
- preinit()
-
- ext = string.lower(os.path.splitext(filename)[1])
-
- try:
-
- if not format:
- format = EXTENSION[ext]
-
- SAVE[string.upper(format)](self, fp, filename)
-
- except KeyError, v:
-
- init()
-
- if not format:
- format = EXTENSION[ext]
-
- SAVE[string.upper(format)](self, fp, filename)
-
- if close:
- fp.close()
-
- def seek(self, frame):
- "Seek to given frame in sequence file"
-
- if frame != 0:
- raise EOFError
-
- def show(self, title = None):
- "Display image (for debug purposes only)"
-
- try:
- import ImageTk
- ImageTk._show(self, title)
- # note: caller must enter mainloop
- except:
- _showxv(self, title)
-
- def split(self):
- "Split image into bands"
-
- ims = []
- self.load()
- for i in range(self.im.bands):
- ims.append(self._makeself(self.im.getband(i)))
- return tuple(ims)
-
- def tell(self):
- "Return current frame number"
-
- return 0
-
- def thumbnail(self, size):
- "Create thumbnail representation (modifies image in place)"
-
- # preserve aspect ratio
- x, y = self.size
- if x > size[0]: y = y * size[0] / x; x = size[0]
- if y > size[1]: x = x * size[1] / y; y = size[1]
- size = x, y
-
- if size == self.size:
- return
-
- self.draft(None, size)
-
- self.load(1)
-
- im = self.resize(size)
-
- self.im = im.im
- self.mode = im.mode
- self.size = size
-
- def transform(self, size, method, data, resample=NEAREST, fill=1):
- "Transform image"
-
- im = new(self.mode, size, None)
- if method == MESH:
- # list of quads
- for box, quad in data:
- im.__transformer(box, self, QUAD, quad, resample, fill)
- else:
- im.__transformer((0, 0)+size, self, method, data, resample, fill)
-
- return im
-
- def __transformer(self, box, image, method, data,
- resample=NEAREST, fill=1):
- "Transform into current image"
-
- # FIXME: this should be turned into a lazy operation (?)
-
- w = box[2]-box[0]
- h = box[3]-box[1]
-
- if method == AFFINE:
- # change argument order to match implementation
- data = (data[2], data[0], data[1],
- data[5], data[3], data[4])
- elif method == EXTENT:
- # convert extent to an affine transform
- x0, y0, x1, y1 = data
- xs = float(x1 - x0) / w
- ys = float(y1 - y0) / h
- method = AFFINE
- data = (x0 + xs/2, xs, 0, y0 + ys/2, 0, ys)
- elif method == QUAD:
- # quadrilateral warp. data specifies the four corners
- # given as NW, SW, SE, and NE.
- nw = data[0:2]; sw = data[2:4]; se = data[4:6]; ne = data[6:8]
- x0, y0 = nw; As = 1.0 / w; At = 1.0 / h
- data = (x0, (ne[0]-x0)*As, (sw[0]-x0)*At,
- (se[0]-sw[0]-ne[0]+x0)*As*At,
- y0, (ne[1]-y0)*As, (sw[1]-y0)*At,
- (se[1]-sw[1]-ne[1]+y0)*As*At)
- else:
- raise ValueError, "unknown transformation method"
-
- if resample not in (NEAREST, BILINEAR, BICUBIC):
- raise ValueError, "unknown resampling filter"
-
- image.load()
-
- self.load(1)
- self.im.transform2(box, image.im, method, data, resample, fill)
-
- def transpose(self, method):
- "Transpose image (flip or rotate in 90 degree steps)"
-
- self.load()
- im = self.im.transpose(method)
- return self._makeself(im)
-
-
- # --------------------------------------------------------------------
- # Delayed operations
-
- class _ImageCrop(Image):
-
- def __init__(self, im, box):
-
- Image.__init__(self)
-
- self.mode = im.mode
- self.size = box[2]-box[0], box[3]-box[1]
-
- self.__crop = box
-
- self.im = im.im
-
- def load(self, modify=0):
-
- # delayed evaluation of crop operation
-
- if self.__crop is None:
- return
-
- # FIXME: the C implementation of crop is broken, so we
- # implement it by pasting into empty image instead.
-
- # im = self.im.__crop(self.__crop)
-
- im = core.new(self.mode, self.size)
-
- im.paste(self.im, (-self.__crop[0], -self.__crop[1],
- self.im.size[0]-self.__crop[0],
- self.im.size[1]-self.__crop[1]))
-
- if self.mode == "P":
- im.putpalette("RGB", self.im.getpalette())
-
- self.im = im
-
- self.__crop = None
-
-
- # --------------------------------------------------------------------
- # Factories
-
- #
- # Debugging
-
- def _wedge():
- "Create greyscale wedge (for debugging only)"
-
- return Image()._makeself(core.wedge("L"))
-
- #
- # Create/open images.
-
- def new(mode, size, color = 0):
- "Create a new image"
-
- if color is None:
- # don't initialize
- return Image()._makeself(core.new(mode, size))
-
- return Image()._makeself(core.fill(mode, size, color))
-
-
- def fromstring(mode, size, data, decoder_name = "raw", *args):
- "Load image from string"
-
- # may pass tuple instead of argument list
- if len(args) == 1 and isTupleType(args[0]):
- args = args[0]
-
- if decoder_name == "raw" and args == ():
- args = mode
-
- im = new(mode, size)
- im.fromstring(data, decoder_name, args)
- return im
-
- def open(fp, mode = "r"):
- "Open an image file, without loading the raster data"
-
- if mode != "r":
- raise ValueError, "bad mode"
-
- if isStringType(fp):
- import __builtin__
- filename = fp
- fp = __builtin__.open(fp, "rb")
- else:
- filename = ""
-
- prefix = fp.read(16)
-
- preinit()
-
- for i in ID:
- try:
- factory, accept = OPEN[i]
- if not accept or accept(prefix):
- fp.seek(0)
- return factory(fp, filename)
- except SyntaxError:
- pass
-
- init()
-
- for i in ID:
- try:
- factory, accept = OPEN[i]
- if not accept or accept(prefix):
- fp.seek(0)
- return factory(fp, filename)
- except SyntaxError:
- pass
-
- raise IOError, "cannot identify image file"
-
- #
- # Image processing.
-
- def blend(im1, im2, alpha):
- "Interpolate between images."
-
- if alpha == 0.0:
- return im1
- elif alpha == 1.0:
- return im2
- return Image()._makeself(core.blend(im1.im, im2.im, alpha))
-
- def composite(image1, image2, mask):
- "Create composite image by blending images using a transparency mask"
-
- image = image2.copy()
- image.paste(image1, None, mask)
- return image
-
- def eval(image, *args):
- "Evaluate image expression"
-
- return image.point(args[0])
-
- def merge(mode, bands):
- "Merge a set of single band images into a new multiband image."
-
- if getmodebands(mode) != len(bands) or "*" in mode:
- raise ValueError, "wrong number of bands"
- for im in bands[1:]:
- if im.mode != getmodetype(mode) or im.size != bands[0].size:
- raise ValueError, "wrong number of bands"
- im = core.new(mode, bands[0].size)
- for i in range(getmodebands(mode)):
- bands[i].load()
- im.putband(bands[i].im, i)
- return Image()._makeself(im)
-
- # --------------------------------------------------------------------
- # Plugin registry
-
- def register_open(id, factory, accept = None):
- id = string.upper(id)
- ID.append(id)
- OPEN[id] = factory, accept
-
- def register_mime(id, mimetype):
- MIME[string.upper(id)] = mimetype
-
- def register_save(id, driver):
- SAVE[string.upper(id)] = driver
-
- def register_extension(id, extension):
- EXTENSION[string.lower(extension)] = string.upper(id)
-
-
- # --------------------------------------------------------------------
- # Unix display support
-
- def _showxv(self, title = None):
-
- if self.mode == "P":
- file = self.convert("RGB")._dump()
- else:
- file = self._dump()
-
- if title:
- opt = "-name \"%s\"" % title
- else:
- opt = ""
-
- os.system("(xv %s %s; rm -f %s)&" % (opt, file, file))
-